#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <poll.h>

#include "isa.h"

#define MEMSIZE	4096
#define INT_KBD 0x60
#define INT_TIMER 0x40

unsigned char *mem;		// main memory of simulator
struct{
  unsigned int counter;
  unsigned int timeout;
}timer;				// timer controller
unsigned int interrupter = -1;	// interrupt controller
unsigned char kbdin;		// keyboard controller
char mem_content[25];		// translate memory to string
unsigned char reg1, reg2;	// used to install reg id
struct pollfd pfd;		// used to test keyboard input


/*
 * printmem: print content of memory
 * @*mem: pointer of memory which needs to print
 */
void printmem(int index){
  for(int i=0;i<80;i++){
    if(i%16 == 0){
      printf("\n%#8x:\t",i+index);
    }
    if((i+1)%4 == 0)
      printf("%.2x ", mem[index+i]);
    else printf("%.2x", mem[index+i]);
  }
  printf("\n"); 
}

/*
 * print_instr: print the content of memory as form of instructions
 * tmppc: temp value of pc
 */
void print_instr(int tmppc){
  unsigned int temp_imme;

  for(int i=0;i<10;i++){
    switch(mem[tmppc]){
      case 0x0: printf("%#8x\thalt\n",tmppc); tmppc++; return;
      case 0x10: printf("%#8x\tnop\n", tmppc); tmppc++; break;
      case 0x20: printf("%#8x\trrmov %.2x\n",tmppc, mem[tmppc+1]); tmppc+=2; break;
      case 0x30:
	temp_imme = mem[tmppc+2];
	temp_imme += mem[tmppc+3] << 8;
	temp_imme += mem[tmppc+4] << 16;
	temp_imme += mem[tmppc+5] << 24;
	printf("%#8x\tirmov %#x,%s\n", tmppc, temp_imme,
	  reg_table[mem[tmppc+1]&0xf].name); tmppc+=6; break;
      case 0x40: printf("%#8x\tstore\n", tmppc); tmppc+=6; break;
      case 0x50: printf("%#8x\tload\n", tmppc); tmppc+=6; break;
      case 0x60: printf("%#8x\tadd %s,%s\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, reg_table[mem[tmppc+1]&0x0F].name); tmppc+=2; break;
      case 0x61: printf("%#8x\tsub %s,%s\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, reg_table[mem[tmppc+1]&0x0F].name); tmppc+=2; break;
      case 0x62: printf("%#8x\tand %s,%s\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, reg_table[mem[tmppc+1]&0x0F].name); tmppc+=2; break;
      case 0x63: printf("%#8x\tor %s,%s\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, reg_table[mem[tmppc+1]&0x0F].name); tmppc+=2; break;
      case 0x64: printf("%#8x\tnot %s,%s\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, reg_table[mem[tmppc+1]&0x0F].name); tmppc+=2; break;
      case 0x65: printf("%#8x\txor %s,%s\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, reg_table[mem[tmppc+1]&0x0F].name); tmppc+=2; break;
      case 0x66: printf("%#8x\tshl %s,%s\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, reg_table[mem[tmppc+1]&0x0F].name); tmppc+=2; break;
      case 0x67: printf("%#8x\tshr %s,%s\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, reg_table[mem[tmppc+1]&0x0F].name); tmppc+=2; break;
      case 0x70: printf("%#8x\tjmp %#x\n", tmppc, (int) mem[tmppc+1]); tmppc+=5; break;
      case 0x71: printf("%#8x\tje %#x\n", tmppc, (int) mem[tmppc+1]); tmppc+=5; break;
      case 0x72: printf("%#8x\tjl %#x\n", tmppc, (int) mem[tmppc+1]); tmppc+=5; break;
      case 0x73: printf("%#8x\tjg %#x\n", tmppc, (int) mem[tmppc+1]); tmppc+=5; break;
      case 0x74: printf("%#8x\tjne %#x\n", tmppc, (int) mem[tmppc+1]); tmppc+=5; break;
      case 0x75: printf("%#8x\tjle %#x\n", tmppc, (int) mem[tmppc+1]); tmppc+=5; break;
      case 0x76: printf("%#8x\tjge %#x\n", tmppc, (int) mem[tmppc+1]); tmppc+=5; break;
      case 0x80: printf("%#8x\tcall %#x\n", tmppc, (int) mem[tmppc+1]); tmppc+=5; break;
      case 0x81: printf("%#8x\tint %#x\n", tmppc, mem[tmppc+1]); tmppc+=2; break;
      case 0x90: printf("%#8x\tret\n", tmppc); tmppc++; break;
      case 0x91: printf("%#8x\tiret\n", tmppc); tmppc++; break;
      case 0xa0: printf("%#8x\tpush\n", tmppc); tmppc+=2; break;
      case 0xb0: printf("%#8x\tpop\n", tmppc); tmppc+=2; break;
      case 0xc0: printf("%#8x\tin %#x,%s\n", tmppc,
	mem[tmppc+2], reg_table[mem[tmppc+1]&0x0F].name); tmppc+=3; break;
      case 0xd0: printf("%#8x\tout %s,%#x\n", tmppc,
	reg_table[mem[tmppc+1]>>4].name, mem[tmppc+2]); tmppc+=3; break;
      default: printf("err instr:%.2x\n", mem[tmppc]);return;
    }
  }
}

/*
 * printSymbols: 
 */
/*********************NOT impelement***************************
void printSymbols(void){
  for(int i=0;i<10;i++){
    if(0 != strlen(label_table[i].name)){
    printf("%s: %#8x\n",label_table[i].name, label_table[i].addr);
    }
  }
}
*****************************************************************/

/*
 * usage: print the usage of debugger
 */
void usage(void){
  printf("\tsi: run instructions step by step.\n");
  printf("\tl: list the instructions.\n");
  printf("\tc: continue to running.\n");
  printf("\tx: show the content of memory.\n");
  printf("\ti: r(show registers).\n");
  // printf("\tp: symbols(print symbols).\n");
  printf("\tq: quit the bebugger.\n");
}

/* 
 * get_reg_id : two register's id is in one byte(@reg), so need to recognize particular reg id according to @index
 * @reg: a byte data which installs 2 regs
 * @index: 1 for first reg(at top 4 bits), 2 for second reg(at low 4 bits).
 * return: register's id
 */
char get_reg_id(char reg, int index){
  switch (index) {
  case 1:
    return reg >> 4;
  case 2:
    return reg & 0x0F;
  default:
    return 0xFF;
  }
}

/*
 * cpu: execute instruction
 * @tempc: program counter
 * return: next value of pc
 */
int cpu(int tempc){
  int addr1, addr2;

  /* timer counts*/
  ++timer.counter;

  /* memory management*/
  if(reg_table[REG_FLAGS].value & 0x80000000){	// if turn on paging,caculate the physical addr.
    addr1 = ((int *)reg_table[REG_PDIR].value)[tempc>>22];
    addr2 = ((int *)addr1)[tempc<<10>>22] + tempc&0xfff;
    tempc = addr2;
  }

  /* if set IF=1, judge if some interrupt happens */
  if(reg_table[REG_FLAGS].value & 0x200){	// if turn on interrupt
    /* judge timer interrupt*/
    if(timer.timeout != 0){
      if(0 == timer.counter % timer.timeout){
        interrupter = INT_TIMER;
        printf("interrupter: timer\n");
	goto int_instr;
      }
    }
    /* judge keyboard interrupt*/
    pfd.fd = stdin;		// test file descriptor:stdin
    pfd.events = POLLIN;
    if(poll(&pfd, 1, 0)){
      read(stdin, &kbdin, 1);
      interrupter = INT_KBD;
      printf("interrupter: kbd\n");
    }
  }

  switch(mem[tempc]){
    case 0x0:		// halt
      printf("\nsystem halt\n");
      return -1;
    case 0x10:		// nop
      return tempc++;
    case 0x20:		// rrmov %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value = reg_table[reg1].value;
      return tempc += 2;
    case 0x30:		// irmov imme, %reg
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value = mem[tempc+2];
      reg_table[reg2].value += mem[tempc+3] << 8;
      reg_table[reg2].value += mem[tempc+4] << 16;
      reg_table[reg2].value += mem[tempc+5] << 24;
      return tempc += 6;
    case 0x40:		// store %reg1, D(%reg2)
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      addr1 = mem[tempc+2];
      addr1 += mem[tempc+3] << 8;
      addr1 += mem[tempc+4] << 16;
      addr1 += mem[tempc+5] << 24;
      addr1 += reg_table[reg2].value;
      mem[addr1] = reg_table[reg1].value & 0xFF;
      mem[addr1 + 1] = (reg_table[reg1].value >> 8) & 0xFF;
      mem[addr1 + 2] = (reg_table[reg1].value >> 16) & 0xFF;
      mem[addr1 + 3] = (reg_table[reg1].value >> 24) & 0xFF;
      return tempc += 6;
    case 0x50:		// load D(%reg1), %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      addr1 = reg_table[reg1].value;
      addr1 += mem[tempc+2];
      addr1 += mem[tempc+3] << 8;
      addr1 += mem[tempc+4] << 16;
      addr1 += mem[tempc+5] << 24;
      reg_table[reg2].value = mem[addr1];
      reg_table[reg2].value += mem[addr1+1] << 8;
      reg_table[reg2].value += mem[addr1+2] << 16;
      reg_table[reg2].value += mem[addr1+3] << 24;
      return tempc += 6;
    case 0x60:		// add %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value += reg_table[reg1].value;
      return tempc += 2;
    case 0x61:		// sub %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value -= reg_table[reg1].value;
      return tempc += 2;
    case 0x62:		// and %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value &= reg_table[reg1].value;
      return tempc += 2;
    case 0x63:		// or %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value |= reg_table[reg1].value;
      return tempc += 2;
    case 0x64:		// not %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value = !reg_table[reg1].value;
      return tempc += 2;
    case 0x65:		// xor %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value ^= reg_table[reg1].value;
      return tempc += 2;
    case 0x66:		// shl %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value >>= reg_table[reg1].value;
      return tempc += 2;
    case 0x67:		// shr %reg1, %reg2
      reg1 = get_reg_id(mem[tempc+1],1);
      reg2 = get_reg_id(mem[tempc+1],2);
      reg_table[reg2].value <<= reg_table[reg1].value;
      return tempc += 2;
    case 0x70:				// jmp Dest
      return tempc = (int) mem[tempc+1];
    case 0x71:				// je Dest
      if(reg_table[REG_FLAGS].value & 0x40)	// ZF == 1
	return tempc = (int) mem[tempc+1];
    case 0x72:					// jl Dest
      if(reg_table[REG_FLAGS].value ^ 0x880)		// (SF xor OF) == 1
	return tempc = (int) mem[tempc+1];
    case 0x73:					// jg Dest
      if(0 == ((reg_table[REG_FLAGS].value & 0x40) | (reg_table[REG_FLAGS].value ^ 0x880)))	// ((SF xor OF) or ZF) == 0
	return tempc = (int) mem[tempc+1];
    case 0x74:					// jne Dest
      if(0 == (reg_table[REG_FLAGS].value & 0x40))	// ZF == 0
	return tempc = (int) mem[tempc+1];
    case 0x75:					// jle Dest
      if((reg_table[REG_FLAGS].value & 0x40) | (reg_table[REG_FLAGS].value ^ 0x880))	// ((SF xor OF) or ZF) == 1
	return tempc = (int) mem[tempc+1];
    case 0x76:					// jge Dest
      if(0 == (reg_table[REG_FLAGS].value ^ 0x880))	// (SF xor OF) == 0
	return tempc = (int) mem[tempc+1];
    case 0x80:		// call Dest
      tempc += 5;
      mem[reg_table[REG_SP].value - 4] = tempc & 0xFF;
      mem[reg_table[REG_SP].value - 3] = (tempc >> 8) & 0xFF;
      mem[reg_table[REG_SP].value - 2] = (tempc >> 16) & 0xFF;
      mem[reg_table[REG_SP].value - 1] = (tempc >> 24) & 0xFF;
      reg_table[REG_SP].value -= 4;
      return tempc = (int) mem[tempc-4];
    case 0x81:		// int Dest
      reg_table[REG_FLAGS].value &= !0x200;			// stop interrupt
      interrupter = mem[tempc+1];				// interruption source
      tempc += 2;						// push pc
int_instr:
      mem[reg_table[REG_SP].value - 4] = tempc & 0xFF;
      mem[reg_table[REG_SP].value - 3] = (tempc >> 8) & 0xFF;
      mem[reg_table[REG_SP].value - 2] = (tempc >> 16) & 0xFF;
      mem[reg_table[REG_SP].value - 1] = (tempc >> 24) & 0xFF;
      reg_table[REG_SP].value -= 4;
      for(int i=0;i<4;i++){		// push registers
        mem[reg_table[REG_SP].value - 4] = reg_table[i].value & 0xFF;
        mem[reg_table[REG_SP].value - 3] = (reg_table[i].value >> 8) & 0xFF;
        mem[reg_table[REG_SP].value - 2] = (reg_table[i].value >> 16) & 0xFF;
        mem[reg_table[REG_SP].value - 1] = (reg_table[i].value >> 24) & 0xFF;
        reg_table[REG_SP].value -= 4;
      }
      return reg_table[REG_IH].value;
    case 0x90:		// ret
      tempc = mem[reg_table[REG_SP].value + 0];
      tempc += mem[reg_table[REG_SP].value + 1] << 8;
      tempc += mem[reg_table[REG_SP].value + 2] << 16;
      tempc += mem[reg_table[REG_SP].value + 3] << 24;
      reg_table[REG_SP].value += 4;
      return tempc;
    case 0x91:		// iret
      for(int i=3;i>=0;i--){		// pop registers
      reg_table[i].value = mem[reg_table[REG_SP].value];
      reg_table[i].value += mem[reg_table[REG_SP].value+1] << 8;
      reg_table[i].value += mem[reg_table[REG_SP].value+2] << 16;
      reg_table[i].value += mem[reg_table[REG_SP].value+3] << 24;
      // printf("pop sp:%x to reg:%s(%x)\n",reg_table[REG_SP].value, reg_table[i].name, reg_table[i].value);
      reg_table[REG_SP].value += 4;
      }
      tempc = mem[reg_table[REG_SP].value];			// pop pc
      tempc += mem[reg_table[REG_SP].value + 1] << 8;
      tempc += mem[reg_table[REG_SP].value + 2] << 16;
      tempc += mem[reg_table[REG_SP].value + 3] << 24;
      reg_table[REG_SP].value += 4;
      interrupter = -1;						// clear interruption source
      return tempc;
    case 0xa0:		// push %reg
      reg1 = get_reg_id(mem[tempc+1], 1);
      mem[reg_table[REG_SP].value - 4] = reg_table[reg1].value & 0xFF;
      mem[reg_table[REG_SP].value - 3] = (reg_table[reg1].value >> 8) & 0xFF;
      mem[reg_table[REG_SP].value - 2] = (reg_table[reg1].value >> 16) & 0xFF;
      mem[reg_table[REG_SP].value - 1] = (reg_table[reg1].value >> 24) & 0xFF;
      reg_table[REG_SP].value -= 4;
      return tempc += 2;
    case 0xb0:		// pop %reg
      reg1 = get_reg_id(mem[tempc+1], 1);
      reg_table[reg1].value = mem[reg_table[REG_SP].value];
      reg_table[reg1].value += mem[reg_table[REG_SP].value+1] << 8;
      reg_table[reg1].value += mem[reg_table[REG_SP].value+2] << 16;
      reg_table[reg1].value += mem[reg_table[REG_SP].value+3] << 24;
      reg_table[REG_SP].value += 4;
      return tempc += 2;
    case 0xc0:		// in imme, %reg
      reg2 = get_reg_id(mem[tempc+1],2);
      if(3 == mem[tempc+2]){	// port 3:memory controller
        reg_table[reg2].value = MEMSIZE; 
      }
      if(0 == mem[tempc+2]){	// port 0:keyboard controller
	reg_table[reg2].value = kbdin;
      }
      return tempc += 3;
    case 0xd0:		// out %reg, imme
      reg1 = get_reg_id(mem[tempc+1],1);
      if(1 == mem[tempc+2]){	// port 1:display controller
        fwrite(&reg_table[reg1].value, 1, 1, stdout);
      }
      if(4 == mem[tempc+2]){	// port 4:timer contruller
	timer.timeout = reg_table[reg1].value;
      }
      return tempc += 3;
    default:
      sprintf(mem_content,"%x", mem[tempc]);
      printf("err: pc=%d mem=%s\n",tempc, mem_content);
      tempc++;
  }
}

int main(int argc, char **argv){
  char *bi_fname;
  FILE *bi_file;
  struct stat statbuff;		// used to get file length
  int pc = 0;			// program counter

  mem = (unsigned char *)malloc(MEMSIZE);
  if(NULL == mem){
    printf("mem alloc err!\n");
    exit(0);
  }

  /* open binary file */
  bi_fname = argv[1];
  bi_file = fopen(bi_fname, "rb+");
  if(bi_file == NULL){
    printf("open binary file err: %s\n", bi_fname);
    free(mem);
    exit(0);
  }

  /* load file to mem */
  stat(bi_fname, &statbuff);
  if(statbuff.st_size !=fread(mem, 1, statbuff.st_size, bi_file)){
    printf("load file err\n");
  }
  fclose(bi_file);

  /* run from pc=0 until HALT or cycle<20 */
  usage();
  printf("For help, type \"help\"\n");
  while(1){
    char buf[10], *cmd, cmdbuf[10];	// to store the command which user input

    printf("(dbg)");
    scanf("%[a-z \tA-Z0-9]", buf);
    strcpy(cmdbuf, buf);
    setbuf(stdin, NULL);		// clear buffer
    cmd = strtok(cmdbuf, " \t");
    // printf("buf:%s, cmdbuf:%s, cmd:%s.\n",buf, cmdbuf, cmd);

    if(0 == strcmp(cmd, "help")){
      usage();
      continue;
    } 
    if(0 == strcmp(cmd, "si")){
      pc = cpu(pc);
      if(pc == -1) pc = 0;
      continue;
    }
    if(0 == strcmp(cmd, "l")){
      print_instr(pc);
      continue;
    }
/**********not implement**************
    if(0 == strcmp(cmd, "p")){
      cmd = strtok(NULL, " \t");
      if(!cmd){
	printf("cmd err: p\n");
	continue;
      }
      if(0 == strcmp(cmd, "symbols")){
        printSymbols();
      }else{
        printf("parameter err: %s\n",cmd);
      }
      continue;
    }
****************************************/
    if(0 == strcmp(cmd, "c")){
      while(pc != -1) pc = cpu(pc);
      continue;
    }
    if(0 == strcmp(cmd, "x")){
      cmd = strtok(NULL, " \t");
      if(!cmd){
        printmem(pc);
      }
      else{
        printmem(strtol(cmd, NULL, 16));
      }
      continue;
    }
    if(0 == strcmp(cmd, "i")){
      cmd = strtok(NULL, " \t");
      if(!cmd){
	printf("cmd err: i\n");
	continue;
      }
      if(0 == strcmp(cmd, "r")){
        for(int i=0;i<REG_NONE;i++){
	  printf("%s:%x  ",reg_table[i].name, reg_table[i].value);
        }
        printf("%%pc:%x\n",pc);
      }else{
        printf("parameter err: %s\n",cmd);
      }
      continue;
    }
    if(0 == strcmp(cmd, "q")){
      goto exit;
    }
    else{
      printf("this command is not correct.\n");
    }
  }
exit:
  free(mem);
}
